package Agent;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Vector;

import org.rtscup.client.adk.Agent;
import org.rtscup.core.Building;
import org.rtscup.core.Entity;
import org.rtscup.core.Map;
import org.rtscup.core.Obstacle;
import org.rtscup.core.Unit;
import org.rtscup.core.id.TypeId;
import org.rtscup.util.IDGenerator;

import Basic.Point;
import Basic.StaticObstacle;
import Basic.Status;
import Basic.Tiles;
import Game.BlackboardTwo;

public class TankAgent extends Agent {

	private Status status;

	private Map map;

	private Tiles tiles;

	private BlackboardTwo blackboard;

	private String team;

	private StaticObstacle[] obstacles;
	private Building[] ourBuildings;
	private Building[] enemyBuildings;
	private Unit[] enemies;
	private int[] units;

	private int bestEnemyBuilding;
	private Point nextPoint;

	private int lastAttackCount;

	private HashMap<Integer, Integer> enemyBuildingsMark;

	public TankAgent(String name, String team) {
		super(name, team, IDGenerator.generateID());
		this.team = team;
		this.status = Status.DECIDING_ENEMY_BUILDING;
		this.tiles = null;
		this.lastAttackCount = 0;
		this.nextPoint = new Point(0, 0);
		this.bestEnemyBuilding = -1;
	}

	@Override
	public void init() {
		Vector<Entity> allEntities = this.getMemory().getAll().getList();
		Entity[] entities = new Entity[allEntities.size()];
		allEntities.toArray(entities);

		ArrayList<StaticObstacle> obstacles = new ArrayList<StaticObstacle>();
		ArrayList<Building> ourBuildings = new ArrayList<Building>();
		ArrayList<Building> enemyBuildings = new ArrayList<Building>();
		ArrayList<Unit> enemies = new ArrayList<Unit>();
		ArrayList<Integer> units = new ArrayList<Integer>();

		for (int i = 0; i < entities.length; i++) {
			Entity e = entities[i];
			if (e.getType() == TypeId.TYPE_MAP) {
				this.map = (Map) e;
			} else if (e.getType() == TypeId.TYPE_OBSTACLE) {
				Obstacle o = (Obstacle) e;
				StaticObstacle so = new StaticObstacle(o.getX(), o.getY(), o.getWidth(), o.getHeight());
				obstacles.add(so);
			} else if (e.getType() == TypeId.TYPE_BUILDING) {
				Building b = (Building) e;
				StaticObstacle so = new StaticObstacle(b.getX(), b.getY(), b.getWidth(), b.getHeight());
				obstacles.add(so);
				if (b.getTeam().equalsIgnoreCase(this.team)) {
					ourBuildings.add(b);
				} else {
					enemyBuildings.add(b);
				}
			} else if (e.getType() == TypeId.TYPE_UNIT) {
				Unit u = (Unit) e;
				if (u.getTeam().equalsIgnoreCase(this.team)) {
					units.add(e.getId());
				} else {
					enemies.add(u);
				}
			}
		}

		this.obstacles = new StaticObstacle[obstacles.size()];
		this.ourBuildings = new Building[ourBuildings.size()];
		this.enemyBuildings = new Building[enemyBuildings.size()];
		this.enemies = new Unit[enemies.size()];
		this.units = new int[units.size()];

		obstacles.toArray(this.obstacles);
		ourBuildings.toArray(this.ourBuildings);
		enemyBuildings.toArray(this.enemyBuildings);
		enemies.toArray(this.enemies);

		for (int i = 0; i < units.size(); i++) {
			this.units[i] = units.get(i);
		}

		Unit self = (Unit) this.getSelf();
		this.tiles = new Tiles(this.map.getWidth(), this.map.getHeight(), this.map.getWidth() / self.getWidth(), this.map.getHeight() / self.getHeight(), this.obstacles);

		this.blackboard = BlackboardTwo.getInstance(enemyBuildings);
		this.blackboard.putIntending(self.getId(), new Point(self.getX(), self.getY()));
		this.enemyBuildingsMark = this.blackboard.getEnemyBuildingsMark();
	}

	private void game2() {
		Unit self = (Unit) this.getSelf();

		ArrayList<StaticObstacle> removeLater = new ArrayList<StaticObstacle>();

		for (int i = 0; i < units.length; i++) {
			if (units[i] != self.getId()) {
				Unit u = (Unit) this.getMemory().get(units[i]);
				StaticObstacle so = new StaticObstacle(u.getX(), u.getY(), u.getWidth(), u.getHeight());
				removeLater.add(so);
				this.tiles.addObstacle(so, Tiles.DYNAMIC_OBSTACLE);
			}
		}

		List<Point> intendingPoints = blackboard.getIntendings(self.getId());
		for (Point point : intendingPoints) {
			StaticObstacle so = new StaticObstacle(point.x, point.y, self.getWidth(), self.getHeight());
			removeLater.add(so);
			this.tiles.addObstacle(so, Tiles.DYNAMIC_OBSTACLE);
		}

		int posX = self.getX();
		int posY = self.getY();

		if (isOutOfTile(posX, posY)) {
			this.executeSelfRecover(self, posX, posY);
		} else if (this.status == Status.DECIDING_ENEMY_BUILDING) {
			this.executeDecidingEnemyBuilding(self);
		} else if (this.status == Status.GOING_TO_ENEMY_BUILDING) {
			this.executeGoingToEnemyBuilding(self);
		} else if (this.status == Status.ATTACKING_ENEMY_BUILDING) {
			this.executeAttackingEnemyBuilding(self);
		} else if (this.status == Status.DEFENDING_BUILDING) {
		} else if (this.status == Status.ATTACKING_ENEMY) {
		}
	}

	private void executeSelfRecover(Unit self, int posX, int posY) {

		int tileNum = this.tiles.getTile(posX, posX);
		Point p = this.tiles.getCentral(tileNum);
		List<Integer> neighbors = this.tiles.getAvailableNeighborTiles(posX, posY);
		double value = Math.random();

		if (value > 0.5) {
			this.move(p.x, p.y, 1000);
		} else if (neighbors.size() > 0) {
			double ratio = 0.5 / neighbors.size();

			for (int i = 0; i < neighbors.size(); i++) {
				if (value < (i + 1) * ratio) {
					int tile = neighbors.get(i);
					Point neighborPoint = tiles.getCentral(tile);
					this.move(neighborPoint.x, neighborPoint.y, self.getSpeed());

					System.out.println(self.getId() + ": (" + posX + "," + posY + ") - (" + neighborPoint.x + "," + neighborPoint.y + ") " + (i + 1) + "/" + neighbors.size());

					break;
				}
			}
		}
		this.status = Status.DECIDING_ENEMY_BUILDING;
	}

	private void executeDecidingEnemyBuilding(Unit self) {
		double minDist = Double.MAX_VALUE;
		boolean exist = false;

		for (int i = 0; i < enemyBuildings.length; i++) {
			Building enemyBuilding = enemyBuildings[i];
			if (enemyBuildingsMark.get(enemyBuilding.getId()) > 0) {
				StaticObstacle so = new StaticObstacle(enemyBuilding.getX(), enemyBuilding.getY(), enemyBuilding.getWidth(), enemyBuilding.getHeight());
				this.tiles.removeObstacle(so, Tiles.SIMPLE_OBSTACLE);
				ArrayList<Point> path = this.tiles.aStar(enemyBuilding.getX(), enemyBuilding.getY(), self.getX(), self.getY());
				if (!path.isEmpty()) {
					int dist = path.size();
					if (dist < minDist) {
						minDist = dist;
						this.bestEnemyBuilding = enemyBuilding.getId();
						this.nextPoint = path.get(0);
						exist = true;
					}
				}
				this.tiles.addObstacle(so, Tiles.SIMPLE_OBSTACLE);
			}
		}

		if (exist) {
			this.status = Status.GOING_TO_ENEMY_BUILDING;
		} else {
			// TODO se nao tiver procurar inimigos pra matar ou defender as
			// bases
		}
	}

	private void executeGoingToEnemyBuilding(Unit self) {
		Building building = (Building) this.getMemory().get(this.bestEnemyBuilding);
		int selfX = self.getX();
		int selfY = self.getY();

		int buildX = building.getX();
		int buildY = building.getY();

		double dist = this.distance(selfX, selfY, buildX, buildY);

		if (dist <= self.getAttackRange()) {
			this.status = Status.ATTACKING_ENEMY_BUILDING;
		} else if (enemyBuildingsMark.get(bestEnemyBuilding) > 0) {
			if (this.nextPoint.x != selfX || this.nextPoint.y != selfY) {
				moveAgent(self, nextPoint.x, nextPoint.y);
			} else {
				goToPlace(self, building, selfX, selfY, buildX, buildY, Status.GOING_TO_ENEMY_BUILDING);
			}
		} else {
			this.status = Status.DECIDING_ENEMY_BUILDING;
		}
	}

	private void executeAttackingEnemyBuilding(Unit self) {
		this.enemyBuildingsMark.put(this.bestEnemyBuilding, this.enemyBuildingsMark.get(this.bestEnemyBuilding) - (lastAttackCount));
		if (enemyBuildingsMark.get(this.bestEnemyBuilding) > 0) {
			this.attack(this.bestEnemyBuilding);
			lastAttackCount = self.getAttack();
		} else {
			this.status = Status.DECIDING_ENEMY_BUILDING;
			this.lastAttackCount = 0;
		}
	}

	private double distance(int p1X, int p1Y, int p2X, int p2Y) {
		return Math.sqrt(Math.pow((p1X - p2X), 2) + Math.pow((p1Y - p2Y), 2));
	}

	private void moveAgent(Unit self, int posX, int posY) {
		blackboard.putIntending(self.getId(), new Point(posX, posY));
		this.move(posX, posY, self.getSpeed());
	}

	private void goToPlace(Unit self, Entity ent, int selfX, int selfY, int entX, int entY, Status previousStatus) {
		StaticObstacle so;
		Building bui = (Building) ent;
		so = new StaticObstacle(entX, entY, bui.getWidth(), bui.getHeight());

		this.tiles.removeObstacle(so, Tiles.SIMPLE_OBSTACLE);
		ArrayList<Point> path = this.tiles.aStar(entX, entY, selfX, selfY);
		this.tiles.addObstacle(so, Tiles.SIMPLE_OBSTACLE);
		if (path.isEmpty()) {
			this.status = Status.WAITING;
		} else {
			this.nextPoint = path.get(0);
			moveAgent(self, nextPoint.x, nextPoint.y);
		}
	}

	private boolean isOutOfTile(int posX, int posY) {
		return (posX - 5) % 10 != 0 || (posY - 5) % 10 != 0;
	}

	@Override
	public void act() {
		this.game2();
	}

	@Override
	public Agent clone() {
		TankAgent clone = new TankAgent(this.getName(), this.getTeam());
		clone.setActive(this.isActive());
		clone.setPolicy(this.getPolicy());
		return clone;
	}

	@Override
	public void senseChange(Entity arg0) {
	}

	@Override
	public void senseEntity(Entity arg0, Entity arg1) {
	}
}
